/*
 * Copyright (C) 2010-2011 sunjumper@163.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package mfinder.config;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import mfinder.ActionFactory;
import mfinder.ActionInvocation;
import mfinder.impl.DefaultActionFactory;
import mfinder.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * 启动mfinder容器的入口配置类。通过Configuration类加载mfinder的配置文件（默认为mfinder.xml）初始化ActionFactory
 * 及加载相应的属性配置，最终得到ActionFactory具体实例。
 * 如果mfinder.xml中未指明ActionFactory的具体实现类，则默认使用{@link DefaultActionFactory }。
 * 通常如下使用：
 * <code><blockquote><pre>
 * Configuration config = new Configuration().load();
 * ActionFactory factory = config.getFactory();
 * factory...
 * </pre></blockquote></code>
 *
 * @see #getFactory()
 */
public class Configuration {

    /* 日志记录 */
    private static final Logger LOG = LoggerFactory.getLogger(Configuration.class);

////////////////////////////////////////////////////////////////////////////////
//                           xml配置文件元素                                   //
////////////////////////////////////////////////////////////////////////////////
    /** 配置文件中表示常量的标签名 */
    public static final String CONSTANT = "constant";

    /** 配置文件中表示类名称的标签属性 */
    public static final String CLASS = "class";

    /** 配置文件中表示值的标签属性 */
    public static final String VALUE = "value";

    /** 配置文件中表示名称的标签属性 */
    public static final String NAME = "name";

    /** 配置文件中表示拦截器的标签名 */
    public static final String INTERCEPTOR = "interceptor";

    /** 配置文件中表示拦截栈的标签名 */
    public static final String INTERCEPTOR_STACK = "interceptor-stack";

    /** 配置文件中表示结果类型的标签名 */
    public static final String RESULT = "result";

    /** 配置文件中表示Action的标签名 */
    public static final String ACTION = "action";

    /** 配置文件中表示包含其它配置的标签名 */
    public static final String INCLUDE = "include";

    /** 配置文件中表示文件名称的标签属性 */
    public static final String FILE = "file";

    /** factory */
    private ActionFactory<?> factory;

    /** constant key and value */
    private Map<String, String> variables = new HashMap<String, String>();

    /**
     * 加载默认配置文件mfinder.xml。
     *
     * @return 此配置对象的引用。
     *
     * @throws ConfigurationException 如果发生配置错误。
     */
    public Configuration load() throws ConfigurationException {
        try {
            load(getResourceAsStream("mfinder.xml"));
            LOG.info("Configuration initiate " + Configuration.this + " from /mfinder.xml");
        } catch (ConfigurationException e) {
            LOG.error("Could not load mfinder settings from /mfinder.xml");
            throw e;
        }
        return this;
    }

    /**
     * 从指定资源获取流数据。
     *
     * @param name 资源文件名。
     *
     * @return 流数据。
     */
    private static InputStream getResourceAsStream(String name) {
        return Configuration.class.getClassLoader().getResourceAsStream(name);
    }

    /**
     * 打印分隔符。
     *
     * @param bool 是否打印。
     */
    private static void printSeparator(boolean bool) {
        if (bool)
            System.out.println("--------------------------------------------------------------------------------");
    }

    /**
     * 加载配置。
     *
     * @param stream InputStream。
     *
     * @return 此配置对象的引用。
     *
     * @throws ConfigurationException 如果发生配置错误。
     */
    public Configuration load(InputStream stream) throws ConfigurationException {

        Document doc = null;
        try {
            doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream);

            NodeList list = null;
            Element e = null;
            int length = 0;

            //constant
            list = doc.getElementsByTagName(CONSTANT);
            length = list.getLength();
            //factory = new DefaultActionFactory();
            for (int i = 0; i < length; i++) {
                e = (Element) list.item(i);
                String name = e.getAttribute(NAME);
                //加载常量
                String val = e.getAttribute(VALUE);
                if (!StringUtil.isNull(val)) {
                    variables.put(name, val);
                } else {
                    String cls = e.getAttribute(CLASS);
                    if (!StringUtil.isNull(cls)) {
                        variables.put(name, cls);
                    }
                }
            }

            String prop = variables.get("ActionFactory");
            //create ActionFactory
            factory = StringUtil.isNull(prop)
                    ? new DefaultActionFactory()
                    : (ActionFactory) Class.forName(prop).newInstance();

            //set properties for ActionFactory
            prop = variables.get("DefaultInterceptorStack");
            if (!StringUtil.isNull(prop))
                factory.setDefaultInterceptorStack(prop);
            prop = variables.get("DefaultResultType");
            if (!StringUtil.isNull(prop))
                factory.setDefaultResultType(prop);

            //give subclasses a chance to prepare factory
            afterActionFactoryCreation(factory);

            printSeparator(length > 0);

            addIncludedProperties(doc);

            //include
            list = doc.getElementsByTagName(INCLUDE);
            length = list.getLength();
            Collection coll = new HashSet<String>();
            for (int i = 0; i < length; i++) {
                e = (Element) list.item(i);

                //add included files, use a hash set to avoid circular reference
                addProperties(e.getAttribute(FILE), coll);
            }

        } catch (ConfigurationException e) {
            throw e;
        } catch (Exception e) {
            throw new ConfigurationException("Could not load or parse properties from input stream", e);
        }
        return this;
    }

    /**
     * 添加包含的配置文件, 用一个集合类判断并避免循环引用。
     *
     * @param name 配置文件名称。
     * @param coll 指定的集合。
     */
    private void addProperties(String name, Collection<String> coll) {
        LOG.info("Load included file : " + name);
        printSeparator(true);

        InputStream stream = getResourceAsStream(name);
        if (stream == null) {
            throw new IllegalArgumentException("Can't load input stream from included file : " + name);
        }
        //if circular reference
        if (coll.contains(name)) {
            throw new IllegalArgumentException("Load circular reference file : " + name);
        }
        coll.add(name);

        Document doc = null;
        try {
            doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(stream);

            //add included properties
            addIncludedProperties(doc);

            //include
            NodeList list = doc.getElementsByTagName(INCLUDE);
            int length = list.getLength();
            for (int i = 0; i < length; i++) {
                //System.out.println(e.getAttribute(FILE));
                //recursive add included files
                addProperties(((Element) list.item(i)).getAttribute(FILE), coll);
            }

        } catch (Exception e) {
            throw new ConfigurationException("Could not load or parse properties from included file : " + name, e);
        } finally {
            if (stream != null)
                try {
                    stream.close();
                } catch (IOException e) {
                    LOG.error("fail to close input stream " + name, e);
                }
        }
    }

    /**
     * 依次添加interceptor、interceptorStack、result、action。
     *
     * @param doc 解析的文档。
     *
     * @throws ClassNotFoundException 如果发生没有找到具有指定名称的类。
     */
    private void addIncludedProperties(Document doc) throws ClassNotFoundException {
        NodeList list = null;
        Element e = null;
        int length = 0;
        //interceptor
        list = doc.getElementsByTagName(INTERCEPTOR);
        length = list.getLength();
        for (int i = 0; i < length; i++) {
            e = (Element) list.item(i);
            //System.out.println(e.getAttribute(CLASS));
            //add interceptor
            factory.addInterceptors(Class.forName(e.getAttribute(CLASS)));
        }

        printSeparator(length > 0);

        //interceptor-stack
        list = doc.getElementsByTagName(INTERCEPTOR_STACK);
        length = list.getLength();
        for (int i = 0; i < length; i++) {
            e = (Element) list.item(i);

            //System.out.println(INTERCEPTOR_STACK + " : " + e.getAttribute(CLASS));
            //add interceptor
            factory.addInterceptorStacks(Class.forName(e.getAttribute(CLASS)));
        }
        printSeparator(length > 0);

        //result
        list = doc.getElementsByTagName(RESULT);
        length = list.getLength();
        for (int i = 0; i < length; i++) {
            e = (Element) list.item(i);

            //System.out.println(DEFAULT_INTERCEPTOR_STACK + " : " + e.getAttribute(NAME));
            //add result types
            factory.addResultTypes(Class.forName(e.getAttribute(CLASS)));
        }

        printSeparator(length > 0);

        //action
        list = doc.getElementsByTagName(ACTION);
        length = list.getLength();
        for (int i = 0; i < length; i++) {
            e = (Element) list.item(i);

            //System.out.println(e.getAttribute(CLASS));
            //add actions
            factory.addActions(Class.forName(e.getAttribute(CLASS)));
        }
        printSeparator(length > 0);
    }

    /**
     * 返回ActionFactory。
     *
     * @param <T> Action工厂对象的类型。
     *
     * @return ActionFactory。
     */
    public <T extends ActionFactory<?>> T getFactory() {
        return (T) factory;
    }

    /**
     * 用于子类继承, 在初始化ActionFactory前执行设置其一些特定的操作。
     * 默认情况下加载ActionFactory额外的配置。
     *
     * @param factory 未初始化的{@link ActionFactory}。
     */
    protected void afterActionFactoryCreation(ActionFactory<?> factory) throws
            ClassNotFoundException {
        String prop = variables.get("extension");
        if (!StringUtil.isNull(prop) && factory instanceof DefaultActionFactory) {
            ((DefaultActionFactory) factory).setExtension(prop);
        }
        prop = variables.get("actionInvocationClass");
        if (!StringUtil.isNull(prop) && factory instanceof DefaultActionFactory) {
            ((DefaultActionFactory) factory).setActionInvocationClass((Class<? extends ActionInvocation>) Class.forName(prop));
        }
        prop = variables.get("actionCacheNumber");
        if (!StringUtil.isNull(prop) && factory instanceof DefaultActionFactory) {
            int cacheNumber = Integer.parseInt(prop);
            if (cacheNumber > 0)
                ((DefaultActionFactory) factory).setActionCacheNumber(cacheNumber);
        }
    }

    /**
     * 返回常量的名称和值。
     *
     * @return 常量的名称和值。
     */
    public Map<String, String> getVariables() {
        return variables;
    }
}
